const bluebird = require('bluebird')
const moment   = require('moment')

const config = require('config')

bluebird.config({
	cancellation: true,
})

const axios = require('axios')


const parse = x => parseInt(x, 10)

const Uri = class {

	constructor ({ uri, status }) {
		this.uri    = uri
		this.status = status
	}

}

const File = class {

	constructor ({ index, path, length, completedLength, selected, uris }) {
		this.index           = index
		this.path            = path
		this.length          = parse(length)
		this.completedLength = parse(completedLength)
		this.uris            = uris.map(x => new Uri(x))
		this.selected        = {
			true : true,
			false: false,
		}[selected]
	}

}

const DownItem = class {

	constructor ({ gid, status, totalLength, completedLength, uploadLength, downloadSpeed, uploadSpeed, connections, errorCode, errorMessage, dir, files, bittorrent }, global) {
		this.gid             = gid
		this.status          = status
		this.totalLength     = parse(totalLength)
		this.uploadLength    = parse(uploadLength)
		this.connections     = parse(connections)
		this.errorCode       = parse(errorCode)
		this.errorMessage    = errorMessage
		this.dir             = dir
		this.bittorrent      = bittorrent
		this.files           = files.map(x => new File(x))
		this.downloadSpeed   = parse(downloadSpeed)
		this.uploadSpeed     = parse(uploadSpeed)
		this.completedLength = parse(completedLength)

		this.global          = global
	}

	get restTime () {
		if (this.completedLength === this.totalLength) return 0

		if (this.downloadSpeed === 0) return Infinity

		return (this.totalLength - this.completedLength) / this.downloadSpeed
	}

	get restLength () {
		return this.totalLength - this.completedLength
	}

	get restDurationHumanize () {
		if (this.restTime === 0) return '∞'

		return moment.duration(this.restTime, 'seconds').humanize()
	}


	getOption () {
		return this.api('aria2.getOption', this.gid)
	}

	reAddUris () {
		/*eslint consistent-return: "off"*/
		if (this.bittorrent) return

		return Promise.all([
			this.getOption(),
			this._removeDownloadResult(),
		])
			.then(
				data => {
					this.addUri(
						this.files[0].uris.map(x => x.uri),
						data[0].data.result
					)
				}
			)
	}

	remove () {
		if (this.status === 'active') {
			return this._remove()
		}

		return this._removeDownloadResult()
	}

	addUri (...args) {
		return this.api('aria2.addUri', ...args)
	}

	unpause () {
		return this.api('aria2.unpause', this.gid)
	}

	pause () {
		return this.api('aria2.pause', this.gid)
	}

	_remove () {
		return this.api('aria2.remove', this.gid)
	}

	_removeDownloadResult () {
		return this.api('aria2.removeDownloadResult', this.gid)
	}

	api (method, ...params) {
		const { host, port, path } = this.global
		const url = `${host}:${port}${path}`


		return new bluebird((resolve, reject) => {
			axios.post(
				url,
				{
					jsonrpc: '2.0',
					id     : 'kxh',
					method,
					params,
				}
			)
				.then(resolve)
				.catch(reject)
		})
	}

}

const OptionBucket = class {

	constructor (options) {
		Object.assign(this, options)
	}

}

const Server = `${config.host}:${config.port}${config.path}`

module.exports = {
	computed: {
		numActive: {
			get () {
				return this.$store.state.list.numActive
			},
		},
		numWaiting: {
			get () {
				return this.$store.state.list.numWaiting
			},
		},
		numStopped: {
			get () {
				return this.$store.state.list.numStopped
			},
		},
		downloadSpeed: {
			get () {
				return this.$store.state.downloadSpeed
			},
			set (v) {
				this.$store.state.downloadSpeed = v
			},
		},
	},
	methods: {
		_api (server, method, ...params) {
			const { host, port, path } = server
			const url = `${host}:${port}${path}`
			const p = new bluebird((resolve, reject) => {
				axios.post(
					url,
					{
						jsonrpc: '2.0',
						id     : 'kxh',
						method,
						params,
					}
				)
					.then(resolve)
					.catch(reject)
			})


			return p
		},
		api (method, ...args) {
			return this._api(
				{
					host: this.$store.state.host,
					port: this.$store.state.port,
					path: this.$store.state.path,
				},
				method,
				...args
			)
		},
		getGlobalOption () {
			return this.api('aria2.getGlobalOption').then(res => new OptionBucket(res.data.result))
		},
		changeGlobalOption (...args) {
			return this.api('aria2.changeGlobalOption', ...args)
		},
		getList () {
			return this.api('aria2.getGlobalStat')
				.then(res => {
					const { numActive, numWaiting, numStopped, downloadSpeed } = res.data.result

					this.$store.commit('stat', {
						numActive    : parse(numActive),
						numWaiting   : parse(numWaiting),
						numStopped   : parse(numStopped),
						downloadSpeed: parse(downloadSpeed),
					})


					return this.api('system.multicall', [
						{
							methodName: 'aria2.tellActive',
							params    : [],
						},
						{
							methodName: 'aria2.tellWaiting',
							params    : [0, this.numWaiting],
						},
						{
							methodName: 'aria2.tellStopped',
							params    : [0, this.numStopped],
						},
					])
				})
				.then(
					res => {
						this.$store.state.list.outdate = false

						this.$store.state.list.data = [].concat(
							res.data.result[0][0],
							res.data.result[1][0],
							res.data.result[2][0]
						)
							.filter(x => {
								if (x.bittorrent) {
									if (x.bittorrent.info) {
										return true
									}

									return false
								}

								return true
							})
							.map(x => new DownItem(x, this.$store.state))
					}
				)
		},
		pauseAll () {
			return this.api('aria2.forcePauseAll')
				.then(this.getList)
		},
		unpauseAll () {
			return this.api('aria2.unpauseAll')
				.then(this.getList)
		},
		shutdown () {
			return this.api('aria2.shutdown')
				.then(this.getList)
		},
		removeAllComplete () {
			const multicall = this.$store.state.list.data
				.filter(x => x.status === 'complete')
				.map(x => ({
					methodName: 'aria2.removeDownloadResult',
					params    : [x.gid],
				}))

			return this.api('system.multicall', multicall)
				.then(this.getList)
		},
		getOption (...args) {
			return this.api('aria2.getOption', ...args).then(res => new OptionBucket(res.data.result))
		},
		addUris (items) {
			const multicall = items.map(x => ({
				methodName: 'aria2.addUri',
				params    : x,
			}))

			return this.api('system.multicall', multicall)
		},
		changeOption (...args) {
			return this.api('aria2.changeOption', ...args)
		},
		tellStatus (...args) {
			return this.api('aria2.tellStatus', ...args)
				.then(res => new DownItem(res.data.result))
		},
		getConnection (id) {
			return axios.get(`${Server}config/${id}`).then(res => {
				this.$store.commit('connection', {
					...res.data,
				})
			})
		},
		createConnection (data) {
			return axios.post(`${Server}config/`, data)
		},
		getAria2Servers () {
			return axios.get(`${Server}config/`)
		},
		login (data) {
			return axios.post(`${Server}login/`, data)
		},
		register (data) {
			return axios.post(`${Server}user/`, data)
		},
	},
}